package gen.tools.jenkins;

import static java.lang.System.out;
import gen.tools.CommitMediator;
import gen.tools.Widgets;
import gen.tools.structures.I1S1;
import gen.tools.structures.S1AS2;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;


public class TriggerNext extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public TriggerNext() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request,response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		final Widgets wid = new Widgets();
		response.setCharacterEncoding( wid.UIcodeSet );
		response.setContentType("application/json");
		
		final int chkInId = wid.getParNumberFromRequest( request.getParameter("chkInId") );
		final String currentJobName = request.getParameter("jobName");					//當前的job名
		final int lastBuildNumber = wid.getLastBuildNumber(currentJobName);			
		final String subsysId = currentJobName.substring(currentJobName.indexOf("_")+1,currentJobName.lastIndexOf("_"));
		final String modId = currentJobName.substring(currentJobName.lastIndexOf("_")+1,currentJobName.length());			//modId
		final String upstreamJobName = wid.stringFilter( request.getParameter("UpJobName") );			//上游jobName
		final int upstreamJobSerialNumber =	wid.getParNumberFromRequest( request.getParameter("UpJobSN") );				//上游jobSN
		
		System.out.println("開始進行TriggerNext\n"+currentJobName+"\n"+modId);
		
		String rootJobName = "";
		int rootJobSerialNumber = 0;
		if ( "".equalsIgnoreCase( upstreamJobName ) ) {
			rootJobName = currentJobName;
			rootJobSerialNumber = lastBuildNumber;
		} else {
			rootJobName = wid.stringFilter( request.getParameter("rootJobName") );				//祖宗jobName
			rootJobSerialNumber = wid.getParNumberFromRequest( request.getParameter("rootJobSerialNumber") );			//祖宗jobSN
		}
		
		String urlStr = wid.JENKINSURL+"job/"+currentJobName+"/config.xml";
		//當前Job的最後job執行序號
		final String performerId = wid.stringFilter( request.getParameter("performerId") );		
		final boolean isDailyBuild = "MR".equalsIgnoreCase( currentJobName.substring(0,3) );
		final String urlEnqeue = wid.JENKINSURL+"queue/api/xml";
		final String urlJobBuilding = wid.JENKINSURL + "computer/api/xml?depth=1";
		
		String sql ="";
		
		
		long deployFileVer = 0;	//最後簽入SVN CM 封裝區中的版本
		String res = "DONE";
		Connection conn = null;
		PreparedStatement ps = null;
		PreparedStatement ps2 = null;
		ResultSet rs = null;
		ResultSet rs2 = null;
		
		SVNCommitInfo info = null;
		try {
			//先將SVN上的版次值回覆
			wid.recoverJenkinsJobSVNver(currentJobName);
			System.out.println("回復Jenkins上的SVN執行版次成功");
			
			conn = wid.getConn(); 
			wid.createBElog(currentJobName, lastBuildNumber,upstreamJobName,upstreamJobSerialNumber,rootJobName,rootJobSerialNumber, chkInId, performerId, isDailyBuild);
			
			/**
			 * triggerNext的動作
			 *	1.	取得目前執行的job號
			 *	2.	取得下游模組清單
			 *		若有下游
			 *			修改目標job的  postBuild 執行 servlet的參數
			 *				a.	chkIn
			 *				b.	父jobName & 父jobSN
			 *		若無下游
			 *			將部署檔簽入 SVN CM 封裝區中，記錄版次
			 *	3.	寫入「BE執行記錄」
			 *		更新自身的 PostBuild裡的URL
			 * */
			
			//	arrMn 記錄的是目前這個job，它下游有那些，若有三個job， 陣列就有三筆
			final ArrayList<S1AS2> ancl1 = wid.getModBEdependencyS1AS2(currentJobName,true,true,urlStr,wid.JENKINS_USR,wid.JENKINS_PWD);
			final boolean isBuildSuccess = "SUCCESS".equalsIgnoreCase( wid.getJenkinsJobResult(currentJobName,lastBuildNumber,"result") );	//自己在jenkins Build版封裝的結果
			
			if ( !isBuildSuccess ) {	//如果Build版封裝失敗，直接就寫log了
				final int beStatusId =4;	//
				wid.updateBEstatus(currentJobName, lastBuildNumber, beStatusId);
			} else {
				//檢查是否存需要並行處理的一級節點
				sql = "SELECT downJobName FROM beparallel WHERE jobName=? AND jobSN=? AND actJobName=?";
				ps = conn.prepareStatement(sql);
				ps.setString(1, upstreamJobName);
				ps.setInt(2, upstreamJobSerialNumber);
				ps.setString(3, currentJobName);
				rs = ps.executeQuery();
				final ArrayList<S1AS2> acl1 = new ArrayList<S1AS2>();	//需要並行處理的一級節點
				while ( rs.next() ) {
					final S1AS2 acl = new S1AS2();
					acl.setJobName( rs.getString("downJobName") );
					acl1.add( acl );
				}
				
				//如果我自己是需要被並行處理的，這時我執行了，便要將我上游的模組，其相依性恢復
				sql = "SELECT jobName,jobSN FROM beparallel WHERE actJobName=? AND actJobSN=? AND downJobName=?";
				ps = conn.prepareStatement(sql);	
				ps.setString(1, upstreamJobName);
				ps.setInt(2, upstreamJobSerialNumber);
				ps.setString(3, currentJobName);
				rs = ps.executeQuery();
				while( rs.next() ) {
					final String rjn = rs.getString("jobName");	// rjn 是 root job name
					final int rootJobSN = rs.getInt("jobSN");
					sql = "SELECT actJobName FROM beparallel WHERE jobName=? AND jobSN=? AND downJobName=?";
					ps2 = conn.prepareStatement(sql);	
					ps2.setString(1, rjn);
					ps2.setInt(2, rootJobSN);
					ps2.setString(3, currentJobName);
					rs2 = ps2.executeQuery();
					while ( rs2.next() ) {
						final String upJN = rs2.getString("actJobName");
						urlStr = wid.JENKINSURL+"job/"+upJN+"/config.xml";						
						String ds = wid.getModBEdependencyString2(true, true,urlStr, wid.JENKINS_USR,wid.JENKINS_PWD);
						if ( "".equalsIgnoreCase(ds) ) {
							ds = currentJobName;
						} else {
							ds = currentJobName+","+ds;
						}
						wid.setJobDownstreamOnJenkins(ds, true, urlStr, wid.JENKINS_USR,wid.JENKINS_PWD);
					}
				}
				
				
				final int ccL1 = acl1.size();	//需要並行處理的一級節點數
				final int ccNL1 = ancl1.size();	//不需要並行處理的一級節點數
				
				out.println("目前這個Job是："+currentJobName+",工作版次是："+lastBuildNumber);
				out.println("上游工作："+upstreamJobName);
				out.println("上游工作版次："+upstreamJobSerialNumber);
				out.println("需要並行處理的一級節點數："+ccL1);
				out.println("不需要並行處理的一級節點數："+ccNL1);
				
				if( ccNL1 ==0 && ccL1==0 ) {	//皆為0，表示是葉節點，要簽入SVN

	            	out.println("成功了，開始試圖簽入SVN");
	            	int beStatusId = 6;	//Build版封裝成功，試圖簽入SVN
	            	wid.updateBEstatus(currentJobName, lastBuildNumber, beStatusId);
	            	InputStream input = null;
	            	Session session = null;
                    ChannelSftp sftp = null;
                    SVNRepository tarRepo = null;
	            	try {
		            	//先辨別，這個模組，是要封裝成 war, 還是 ear，進入SVN 該模組的資料夾去解析pom.xml
	            		ArrayList<String> connInfo = wid.getSVN4SubsysConnInfo(subsysId,2);
	            		
	            		final String extName = wid.getModExt(connInfo.get(0), connInfo.get(1), connInfo.get(2), modId);
	            		
		            	final String depFile = modId+extName;
	            		//檔案的來源--> jenkins運作的主機，我們用SFTP去拿
	            		JSch jsch = new JSch();
	                    JSch.setConfig("StrictHostKeyChecking", "no");
	                    
	                    session = jsch.getSession(wid.ftpUsr, wid.ftpIP, 22);
						session.setPassword( wid.ftpPWD );
				        session.connect();
				        sftp = (ChannelSftp) session.openChannel("sftp");
				        
				        sftp.connect();
						if ( sftp.isConnected() ) {
							sftp.cd("..");
							sftp.cd("..");
							sftp.cd("opt");
							sftp.cd("jenkins");
							sftp.cd(currentJobName);
							sftp.cd("workspace");
							sftp.cd("target");
							final SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
							connInfo = wid.getSVN4SubsysConnInfo(subsysId,7);
							tarRepo = SVNRepositoryFactory.create( SVNURL.parseURIEncoded( connInfo.get(0) ) );
							final ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(connInfo.get(1), connInfo.get(2));
							
							tarRepo.setAuthenticationManager(authManager);
					       
					        /** ISVNEditor 實體 **/
					        final ISVNEditor editor = tarRepo.getCommitEditor(null, new CommitMediator() );
				            editor.openRoot(-1);
			            	input = sftp.get( depFile );
					        try {
					        	editor.openFile( depFile,-1);	
					        } catch(SVNException e) {
					        	editor.addFile( depFile, null, -1);
					        }
					        
				    		editor.applyTextDelta( depFile, null); 
				    		String baseChecksum = deltaGenerator.sendDelta( depFile, input, editor, true);
				    	    editor.textDeltaEnd( depFile );
				    	    editor.closeFile( depFile, baseChecksum );
				    	    info =  editor.closeEdit();
				    	    deployFileVer = info.getNewRevision();
				    	    
				    	    //Build版封裝成功，且簽入SVN成功
				    	    beStatusId = 3;	
		            		wid.updateBEstatus(currentJobName, lastBuildNumber, beStatusId,depFile,deployFileVer);
		            		
		            		/**
		    				 * 加入測試人員的待辦清單，他們要assign部署日期
		    				 ***/
		    				if ( deployFileVer != 0 ) {	//表示有產生部署檔，才有他們待辦的需要
		    					final String masterId = currentJobName + wid.TOKENONE + lastBuildNumber;	//產生這次部署檔的主鍵
		    					wid.genToDoList(subsysId, masterId, performerId, 3, 6);
		    				}
						}
	            	} catch (Exception e) {
	            		wid.errLog(e);
	            		
	            		//Build版封裝成功，但簽入SVN失敗
	            		beStatusId = 5;
	            		wid.updateBEstatus(currentJobName, lastBuildNumber, beStatusId);
	            		e.printStackTrace();
	            	} finally {
	            		if (input != null) { input.close(); }
	            		if (tarRepo != null) { tarRepo.closeSession(); }
	            		if (sftp != null) {
	            			if( sftp.isConnected() ) {
	            				sftp.disconnect();
	            			}
	            		}
	            		if (session != null) {
	            			if( session.isConnected() ) {
	            				session.disconnect();
	            			}
	            		}
	            	}
				} else {
					//將一級節點下相對應的二級節點加入
					setLevel2(acl1);
					setLevel2(ancl1);
					
					for( int i=0; i < acl1.size();i++ ) {	//需要並行處理的一級節點
						
						final String jn = acl1.get(i).getJobName();
						final ArrayList<String> anc = acl1.get(i).getArrNeedCon();			//需要並行處理的一級節點，且需要並行處理的二級節點
						final ArrayList<String> annc = acl1.get(i).getArrNoNeedCon();		//需要並行處理的一級節點，但不需要並行處理的二級節點
						
						
						out.println("目前這個Job是："+currentJobName+",工作版次是："+lastBuildNumber);
						out.println("目前處理的一級節點是："+jn);
						out.println("需要並行處理的二級節點數："+anc.size());
						out.println("不需要並行處理的二級節點數："+annc.size());
						
						//更改 並行表中的值，表示又多完成一筆
						sql = "UPDATE beparallel SET actJobSN=? WHERE jobName=? AND jobSN=? AND actJobName=? AND downJobName=?";
						ps = conn.prepareStatement(sql);
						ps.setInt(1, lastBuildNumber);
						ps.setString(2, upstreamJobName);
						ps.setInt(3, upstreamJobSerialNumber);
						ps.setString(4, currentJobName );
						ps.setString(5, jn );
						ps.execute();
						
						sql = "SELECT COUNT(actJobName) FROM beparallel WHERE jobName=? AND jobSN=? AND downJobName=?";
						ps = conn.prepareStatement(sql);	
						ps.setString(1, upstreamJobName);
						ps.setInt(2, upstreamJobSerialNumber);
						ps.setString(3, jn );
						rs = ps.executeQuery();
						rs.next();
						final int actJobCount = rs.getInt(1);	//必需完成的job數
						System.out.print("執行"+jn+"前必需完成的模組數"+ actJobCount );
						
						//查詢已完成的個數
						sql = "SELECT COUNT(*) FROM beparallel WHERE jobName=? AND jobSN=? AND downJobName=? AND actJobSN<>0";
						ps = conn.prepareStatement(sql);	
						ps.setString(1, upstreamJobName);
						ps.setInt(2, upstreamJobSerialNumber);
						ps.setString(3, jn);
						rs = ps.executeQuery();
						rs.next();
						
						System.out.println("，目前："+ rs.getInt(1) );
						
						if ( rs.getInt(1) == actJobCount ) {	//相同，表示一級節點可以觸發了
							sql = "INSERT INTO beparallel (jobName,jobSN,actJobName,downJobName,cDT) VALUES (?,?,?,?,?)";
							ps = conn.prepareStatement(sql);
							ps.setString(1, currentJobName);
							ps.setInt(2, lastBuildNumber);
							ps.setString(3, jn);
							ps.setTimestamp(5, wid.cDT());
							for( int j=0; j< anc.size(); j++ ) {
								ps.setString(4, anc.get(j));
								ps.execute();
								wid.removeJobDownStreamItem(jn, anc.get(j));	//移除一級節點的相依性
							}
							
							
							//觸發前的檢查
							final boolean isEnqueue = wid.isEnqueue( jn,false,urlEnqeue,"","");
							boolean isJobBuilding = wid.isJobBuilding( jn ,false,urlJobBuilding,"","");	// 下游的job確定沒有在執行 
							if ( !isEnqueue ) {
								while ( isJobBuilding ) {
									//如果正在跑，就一直進行檢查，直到它跑完，while就結束
									isJobBuilding = wid.isJobBuilding( jn ,false,urlJobBuilding,"","");	// 下游的job確定
								}
								wid.updateJobPostBuildURL(jn,chkInId,currentJobName,lastBuildNumber,rootJobName,rootJobSerialNumber,performerId);	//修改下游的PostBuild中URL的值
								
								out.println(currentJobName+"驅動了"+jn);
								wid.triggerJenkinsBuild(jn);					//驅動此一級節點(包含了兩種類型anc, annc)
							}
						}
					}
					
					for( int i=0; i < ancl1.size();i++ ) {	//不需要並行處理的一級節點
						final String jn = ancl1.get(i).getJobName();
						wid.updateJobPostBuildURL(jn,chkInId,currentJobName,lastBuildNumber,rootJobName,rootJobSerialNumber,performerId);	//修改下游的PostBuild中URL的值
						final ArrayList<String> anc = ancl1.get(i).getArrNeedCon();			//不需要並行處理的一級節點，但需要並行處理的二級節點
						//final ArrayList<String> annc = ancl1.get(i).getArrNoNeedCon();		//不需要並行處理的一級節點，且不需要並行處理的二級節點
						
						sql = "INSERT INTO beparallel (jobName,jobSN,actJobName,downJobName,cDT) VALUES (?,?,?,?,?)";
						ps = conn.prepareStatement(sql);
						ps.setString(1, currentJobName);
						ps.setInt(2, lastBuildNumber);
						ps.setString(3, jn);
						ps.setTimestamp(5, wid.cDT());
						for( int j=0; j< anc.size(); j++ ) {
							ps.setString(4, anc.get(j));
							ps.execute();
							wid.removeJobDownStreamItem( jn, anc.get(j) );	//移除一級節點的相依性
						}
					}
				}
			}
			
			
		} catch (Exception e) {
			res = wid.errProcess(e);
			out.println(res);
			wid.errLog(e);
		}  finally {
			try {
				wid.resetJobPostBuildURL(currentJobName);	//執行完畢，將當前Job的postBuild的URL，改回原本的值
				if ( rs !=null ) {rs.close();}
				if ( rs2 !=null ) {rs2.close();}
				if ( ps !=null ) {ps.close();}
				if ( ps2 !=null ) {ps2.close();}
				if ( conn !=null ) {conn.close();}	
			} catch (Exception e) { e.printStackTrace(); }
		}
		response.getWriter().write(res);
	}
	
	public void setLevel2(ArrayList<S1AS2> acl) throws Exception {
		final Map<String,I1S1> map = new HashMap<String,I1S1>();	//記錄二級節點的map
		final Widgets wid  = new Widgets();
		ArrayList<String> arrL2 = null;
		
		for(int i=0; i< acl.size(); i++) {
			final String l1jn = acl.get(i).getJobName().trim();
			final String urlStr = wid.JENKINSURL+"job/"+l1jn+"/config.xml";
			arrL2 = wid.getModBEdependencyString(l1jn,true,true,urlStr,wid.JENKINS_USR,wid.JENKINS_PWD);	//此一級節點下的一級節點
			
			for(int j=0 ; j< arrL2.size(); j++) {
				final String jobName = arrL2.get(j);;
				
				final I1S1 i1s1 = new I1S1();
				i1s1.setS1( jobName );
				if ( map.containsKey( jobName ) ) {		//I1 表示指向此二級節點的一級節點數
					i1s1.setI1( map.get(jobName).getI1()+1 );
				} else {							//沒有值，表示要新增
					i1s1.setI1(1);						
				}
				map.put(jobName, i1s1);					
			}
		}
		for(int i=0; i< acl.size(); i++) {
			final String l1jn = acl.get(i).getJobName();	//一級節點
			final String urlStr = wid.JENKINSURL+"job/"+l1jn+"/config.xml";
			arrL2 = wid.getModBEdependencyString(l1jn,true,true,urlStr,wid.JENKINS_USR,wid.JENKINS_PWD);	//此一級節點下的一級節點
			
			final ArrayList<String> acl2 = new ArrayList<String>();	//需要並行處理的二級節點
			final ArrayList<String> ancl2 = new ArrayList<String>();	//不需要並行處理的二級節點
			
			for(int j=0 ; j< arrL2.size(); j++) {
				
				final String l2jn = arrL2.get(j);
				
				if ( map.get(l2jn).getI1() == 1 ) {
					ancl2.add( l2jn );	//不需要並行處理的二級節點
				} else {
					acl2.add( l2jn );	//需要並行處理的二級節點
				}
								
			}
			acl.get(i).setArrNeedCon( acl2 );
			acl.get(i).setArrNoNeedCon( ancl2 );	
		}
	}
}
